#region Using

using System;
using System.IO;
using System.Web;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.Xml;
using System.Web.Caching;
using BlogEngine.Core.StorageProviders;

#endregion

namespace BlogEngine.Core.Web.HttpHandlers
{
	/// <summary>
	/// The PhotoHandler serves all photo images
	/// </summary>
	/// <remarks>
	/// By using a HttpHandler to serve images, it is very easy
	/// to add the capability to stop bandwidth leeching or
	/// to create a statistics analysis feature upon it.
	/// </remarks>
	public class PhotoHandler : IHttpHandler
	{

		#region IHttpHandler Members

		/// <summary>
		/// Gets a value indicating whether another request can use the <see cref="T:System.Web.IHttpHandler"></see> instance.
		/// </summary>
		/// <value></value>
		/// <returns>true if the <see cref="T:System.Web.IHttpHandler"></see> instance is reusable; otherwise, false.</returns>
		public bool IsReusable
		{
			get { return false; }
		}

		/// <summary>
		/// Enables processing of HTTP Web requests by a custom HttpHandler that implements the <see cref="T:System.Web.IHttpHandler"/> interface.
		/// </summary>
		/// <param name="context">An <see cref="T:System.Web.HttpContext"/> object that provides references to the intrinsic server objects (for example, Request, Response, Session, and Server) used to service HTTP requests.</param>
		public void ProcessRequest(HttpContext context)
		{
			if (!string.IsNullOrEmpty(context.Request.QueryString["photo"]))
				HandleServingPhoto(context);
			else if (!string.IsNullOrEmpty(context.Request.QueryString["polaroid"]))
				HandleServingPolaroid(context);
			else if (!string.IsNullOrEmpty(context.Request.QueryString["slideshow"]))
				HandleServingSlideShow(context);
		}

		/// <summary>
		/// Handles the serving photo.
		/// </summary>
		/// <param name="context">The context.</param>
		private void HandleServingPhoto(HttpContext context)
		{
			string id = context.Request.QueryString["photo"];
			int size = 0;

			if (!string.IsNullOrEmpty(context.Request.QueryString["size"]))
				Int32.TryParse(context.Request.QueryString["size"], out size);

			Guid guid = new Guid(id);

			Photo photo = (guid != Guid.Empty) ? Photo.GetById(guid) : Photo.EmptyPhoto;

			if (photo == null)
			{
				context.Response.Status = "404 Bad Request";
				return;
			}

			OnServing(id);

			// Fix for IE not handling jpg image types
			if (string.Compare(photo.Extension, "JPG") == 0)
				context.Response.ContentType = "image/jpeg";
			else
				context.Response.ContentType = "image/" + photo.Extension;

			string etag = "\"" + photo.Id + "_" + size.ToString() + "\"";
			string incomingEtag = context.Request.Headers["If-None-Match"];

			context.Response.Cache.SetCacheability(HttpCacheability.Public);
			context.Response.Cache.SetExpires(DateTime.Now.AddYears(1));
			context.Response.Cache.SetLastModified(photo.LastModified.ToUniversalTime().AddHours(-1));
			context.Response.Cache.SetETag(etag);

			if (String.Compare(incomingEtag, etag) == 0)
			{
				context.Response.StatusCode = (int)System.Net.HttpStatusCode.NotModified;
				context.Response.End();
			}

			string cachekey = "I:" + id + ":" + size.ToString() + ":" + photo.LastModified.Ticks.ToString();
			Stream newfile = (Stream)HttpContext.Current.Cache[cachekey];

			bool foundInCache = (newfile != null);

			if (!foundInCache)
			{
				Image newimage = null;

				if (guid != Guid.Empty)
				{
					newimage = photo.Image();

					newimage = PhotoManipulation.ResizeImage(newimage, size);

					if (size >= 0)
					{
						foreach (PhotoEffect photoeffect in photo.PhotoEffects)
							newimage = photoeffect.Effect.Apply(newimage, photoeffect.Parameters);

						foreach (PhotoEffect photoeffect in photo.Album.PhotoEffects)
							newimage = photoeffect.Effect.Apply(newimage, photoeffect.Parameters);
					}
				}
				else
				{
					newfile = LoadEmptyPhotoFile(photo);
					if (newfile != null)
					{
						newimage = Image.FromStream(newfile);
						newimage = PhotoManipulation.ResizeImage(newimage, size);
					}
				}

				// save as JPEG with 90% quality
				newfile = PhotoManipulation.SaveToStream(newimage, 90);
			}

			newfile.Seek(0, SeekOrigin.Begin);

			Byte[] buffer = new Byte[4096];

			int bytesRead = newfile.Read(buffer, 0, buffer.Length);
			while (bytesRead > 0)
			{
				context.Response.OutputStream.Write(buffer, 0, bytesRead);
				bytesRead = newfile.Read(buffer, 0, buffer.Length);
			}

			if (!foundInCache)
			{
				newfile.Seek(0, SeekOrigin.Begin);
				HttpContext.Current.Cache.Add(cachekey, newfile, null, DateTime.Now.AddMinutes(60), TimeSpan.Zero, CacheItemPriority.Normal, null);
			}

			OnServed(id);
		}

		/// <summary>
		/// Loads the empty photo file.
		/// </summary>
		/// <param name="photo">The photo.</param>
		/// <returns></returns>
		public Stream LoadEmptyPhotoFile(Photo photo)
		{
			try
			{
				return File.OpenRead(HttpContext.Current.Server.MapPath(photo.Location));
			}
			catch
			{
				return null;
			}
		}

		private void HandleServingPolaroid(HttpContext context)
		{
			string id = context.Request.QueryString["polaroid"];
			Album album = Album.GetById(new Guid(id));

			if (album == null)
			{
				context.Response.Status = "404 Bad Request";
				return;
			}

			int size = BlogSettings.Instance.SlideShowPhotoSize;

			if (!string.IsNullOrEmpty(context.Request.QueryString["size"]))
			{
				Int32.TryParse(context.Request.QueryString["size"], out size);
			}

			context.Response.ContentType = "text/xml";

			using (XmlTextWriter writer = new XmlTextWriter(context.Response.Output))
			{
				writer.Formatting = Formatting.Indented;
				writer.Indentation = 4;

				//write the start of the document
				writer.WriteStartDocument(true);
				writer.WriteStartElement("photos");

				foreach (Photo photo in album.Photos)
				{
					if (photo.IsPublished)
					{
						writer.WriteStartElement("photo");
						writer.WriteAttributeString("desc", photo.Title);
						writer.WriteAttributeString("url", photo.GetSizedPhotoURL(size));
						writer.WriteEndElement();
					}
				}

				//write the end photo tag
				writer.WriteEndDocument();
			}
		}

		private void HandleServingSlideShow(HttpContext context)
		{
			string id = context.Request.QueryString["slideshow"];
			Album album = Album.GetById(new Guid(id));

			if (album == null)
			{
				context.Response.Status = "404 Bad Request";
				return;
			}

			int size = BlogSettings.Instance.SlideShowPhotoSize;

			if (!string.IsNullOrEmpty(context.Request.QueryString["size"]))
			{
				Int32.TryParse(context.Request.QueryString["size"], out size);
			}

			context.Response.ContentType = "text/xml";
			using (XmlTextWriter writer = new XmlTextWriter(context.Response.Output))
			{
				writer.Formatting = Formatting.Indented;
				writer.Indentation = 4;

				//write the start of the document
				writer.WriteStartDocument(true);
				writer.WriteStartElement("playlist");
				writer.WriteAttributeString("version", "1");
				writer.WriteAttributeString("xmlns", string.Empty, null, "http://xspf.org/ns/0/");

				//write the metadata
				writer.WriteElementString("title", album.Title);
				//writer.WriteElementString("creator",BlogSettings.Instance.AuthorName);
				//writer.WriteElementString("info",Utils.AbsoluteWebRoot.ToString());

				//write the tracklist
				writer.WriteStartElement("trackList");

				foreach (Photo photo in Photo.Items)
				{
					if (photo.IsPublished)
					{
						writer.WriteStartElement("track");
						writer.WriteElementString("title", photo.Title);
						writer.WriteElementString("location", photo.GetSizedPhotoURL(size));
						writer.WriteStartElement("meta");
						writer.WriteAttributeString("rel", "type");
						writer.WriteValue("jpg");
						writer.WriteEndElement();
						writer.WriteEndElement();
					}
				}

				//write the end tracklist tag
				writer.WriteEndElement();

				//write the end playlist element
				writer.WriteEndDocument();
			}
		}

		#endregion

		#region Events

		/// <summary>
		/// Occurs when the requested file does not exist;
		/// </summary>
		public static event EventHandler<EventArgs> Serving;
		private static void OnServing(string file)
		{
			if (Serving != null)
			{
				Serving(file, EventArgs.Empty);
			}
		}

		/// <summary>
		/// Occurs when a file is served;
		/// </summary>
		public static event EventHandler<EventArgs> Served;
		private static void OnServed(string file)
		{
			if (Served != null)
			{
				Served(file, EventArgs.Empty);
			}
		}

		/// <summary>
		/// Occurs when the requested file does not exist;
		/// </summary>
		public static event EventHandler<EventArgs> BadRequest;
		private static void OnBadRequest(string file)
		{
			if (BadRequest != null)
			{
				BadRequest(file, EventArgs.Empty);
			}
		}

		#endregion

	}
}
